// Copyright 2016 The kingshard Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package main

import (
	"flag"
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"path"
	"runtime"
	"strings"
	"syscall"

	"github.com/google/gops/agent"
	"github.com/pyroscope-io/client/pyroscope"

	"gitee.com/he3db/he3proxy/config"
	"gitee.com/he3db/he3proxy/core/golog"
	"gitee.com/he3db/he3proxy/monitor"
	"gitee.com/he3db/he3proxy/proxy/server"
	"gitee.com/he3db/he3proxy/web"
)

// Define profile parameters
var configFile = flag.String("config", "./etc/he3proxy.yaml", "He3Proxy config file")

// Define log level parameters
var logLevel = flag.String("log-level", "", "log level [debug|info|warn|error], default error")

// Define version parameters
var version = flag.Bool("v", false, "the version of He3Proxy")

// Define pprof parameters
var pprof = flag.Bool("pprof", false, "the switch of pprof")

// Define pyroscope parameters
var pyro = flag.Bool("pyroscope", false, "the switch of pyroscope")

const (
	sqlLogName = "sql.log"
	sysLogName = "sys.log"
	MaxLogSize = 1024 * 1024 * 1024
)

var (
	BuildDate    string
	BuildVersion string
)

const banner string = `
    __  __    _____       ____                       
   / / / /__ |__  /      / __ \_________  _  ____  __
  / /_/ / _ \ /_ <______/ /_/ / ___/ __ \| |/_/ / / /
 / __  /  __/__/ /_____/ ____/ /  / /_/ />  </ /_/ / 
/_/ /_/\___/____/     /_/   /_/   \____/_/|_|\__, /  
                                            /____/   
`

func main() {

	fmt.Print(banner)
	runtime.GOMAXPROCS(runtime.NumCPU())
	// Get parameters output in CMD
	flag.Parse()
	// Output version information and compilation time are defined in makefile
	fmt.Printf("Git commit:%s\n", BuildVersion)
	fmt.Printf("Build time:%s\n", BuildDate)
	// If there is a version parameter, - V returns directly
	if *version {
		return
	}
	// performance monitor
	if *pprof {
		// pprof
		runtime.SetMutexProfileFraction(1) // Turn on tracing of lock calls
		runtime.SetBlockProfileRate(1)     // Enable tracking of blocking operations
		go func() {
			for port := 6060; port < 6100; port++ {
				_ = http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
			}
		}()

		go func() {
			for port := 6070; port < 6100; port++ {
				_ = agent.Listen(agent.Options{Addr: fmt.Sprintf(":%d", port)})
			}
		}()
	}
	if *pyro {
		go func() {
			pyroscopeAddr := os.Getenv("PYROSCOPE_SERVER")
			if pyroscopeAddr == "" {
				pyroscopeAddr = "http://127.0.0.1:4040"
			}
			pyroscope.Start(pyroscope.Config{
				ApplicationName: "He3Proxy",
				ServerAddress:   pyroscopeAddr,
				Logger:          pyroscope.StandardLogger,
				Tags:            map[string]string{"region": os.Getenv("REGION")},

				// optionally, if authentication is enabled, specify the API key:
				// AuthToken: os.Getenv("PYROSCOPE_AUTH_TOKEN"),

				ProfileTypes: []pyroscope.ProfileType{
					// these profile types are enabled by default:
					pyroscope.ProfileCPU,
					pyroscope.ProfileAllocObjects,
					pyroscope.ProfileAllocSpace,
					pyroscope.ProfileInuseObjects,
					pyroscope.ProfileInuseSpace,

					// these profile types are optional:
					pyroscope.ProfileGoroutines,
					pyroscope.ProfileMutexCount,
					pyroscope.ProfileMutexDuration,
					pyroscope.ProfileBlockCount,
					pyroscope.ProfileBlockDuration,
				},
			})
		}()
	}
	// If the config parameter is specified but the value is empty, a prompt will be thrown;
	// If not, the default value is ./etc/he3proxy.yaml
	if len(*configFile) == 0 {
		fmt.Println("must use a config file")
		return
	}

	cfg, err := config.ParseConfigFile(*configFile)
	if err != nil {
		fmt.Printf("parse config file error:%v\n", err.Error())
		return
	}

	// when the log file size greater than 1GB, a new file will be generated
	if len(cfg.LogPath) != 0 {
		// Splice the sys.log path according to the log path of the configuration file
		sysFilePath := path.Join(cfg.LogPath, sysLogName)
		// Generate a log file, and add write if the file exists; The maximum file size is 1GB;
		// backupCount indicates the number of files allowed to be backed up,
		// that is, the number of files allowed to be scrolled
		sysFile, err := golog.NewRotatingFileHandler(sysFilePath, MaxLogSize, 1)
		if err != nil {
			fmt.Printf("new log file error:%v\n", err.Error())
			return
		}
		// Start cycle output log
		golog.GlobalSysLogger = golog.New(sysFile, golog.Lfile|golog.Ltime|golog.Llevel)

		// configuration sql.log
		sqlFilePath := path.Join(cfg.LogPath, sqlLogName)
		sqlFile, err := golog.NewRotatingFileHandler(sqlFilePath, MaxLogSize, 1)
		if err != nil {
			fmt.Printf("new log file error:%v\n", err.Error())
			return
		}
		golog.GlobalSqlLogger = golog.New(sqlFile, golog.Lfile|golog.Ltime|golog.Llevel)
	}

	if *logLevel != "" {
		setLogLevel(*logLevel)
	} else {
		setLogLevel(cfg.LogLevel)
	}

	var svr *server.Server
	var apiSvr *web.ApiServer
	var prometheusSvr *monitor.Prometheus
	// init server configuration
	svr, err = server.NewServer(cfg)
	if err != nil {
		golog.Error("main", "main", err.Error(), 0)
		golog.GlobalSysLogger.Close()
		golog.GlobalSqlLogger.Close()
		return
	}
	// init web api configuration
	apiSvr, err = web.NewApiServer(cfg, svr)
	if err != nil {
		golog.Error("main", "main", err.Error(), 0)
		golog.GlobalSysLogger.Close()
		golog.GlobalSqlLogger.Close()
		svr.Close()
		return
	}
	// init prometheus configuration
	prometheusSvr, err = monitor.NewPrometheus(cfg.PrometheusAddr, svr)
	if err != nil {
		golog.Error("main", "main", err.Error(), 0)
		golog.GlobalSysLogger.Close()
		golog.GlobalSqlLogger.Close()
		svr.Close()
		return
	}

	// Create a pipeline to receive system signals
	sc := make(chan os.Signal, 1)
	signal.Notify(sc,
		syscall.SIGINT,  // INTR Ctrl+C
		syscall.SIGTERM, // end app
		syscall.SIGQUIT, // QUIT Ctrl+/
		syscall.SIGPIPE, // FIFO/Socket
		syscall.SIGUSR1, // kill -s USR1 {pid} eg: kill -s USR1 14149
	)

	// start a goroutine to deal with signal
	go func() {
		for {
			sig := <-sc
			if sig == syscall.SIGINT || sig == syscall.SIGTERM || sig == syscall.SIGQUIT {
				golog.Info("main", "main", "Got signal", 0, "signal", sig)
				golog.GlobalSysLogger.Close()
				golog.GlobalSqlLogger.Close()
				svr.Close()
			} else if sig == syscall.SIGPIPE {
				golog.Info("main", "main", "Ignore broken pipe signal", 0)
			} else if sig == syscall.SIGUSR1 {
				// reload configuration
				golog.Info("main", "main", "Got update config signal", 0)
				newCfg, err := config.ParseConfigFile(*configFile)
				if err != nil {
					golog.Error("main", "main", fmt.Sprintf("parse config file error:%s", err.Error()), 0)
				} else {
					svr.UpdateConfig(newCfg)
				}
			}
		}
	}()
	// start web api
	go apiSvr.Run()
	// start prometheus
	go prometheusSvr.Run()
	svr.Run(os.Getenv(config.DbTypeEnv))
	golog.Info("server", "onConn", "server working at "+
		os.Getenv(config.DbTypeEnv)+" mode", 0)
}

func setLogLevel(level string) {
	switch strings.ToLower(level) {
	case "debug":
		golog.GlobalSysLogger.SetLevel(golog.LevelDebug)
	case "info":
		golog.GlobalSysLogger.SetLevel(golog.LevelInfo)
	case "warn":
		golog.GlobalSysLogger.SetLevel(golog.LevelWarn)
	case "error":
		golog.GlobalSysLogger.SetLevel(golog.LevelError)
	default:
		golog.GlobalSysLogger.SetLevel(golog.LevelError)
	}
}
